<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
  <div id="mountNode"></div>

</head>
<body>
<script src="../build/g6.js"></script>
<script src="../build/minimap.js"></script>
<script src="./assets/jquery-3.2.1.min.js"></script>
<script>
  const ERROR_COLOR = "#F5222D";

  const SIMPLE_TREE_NODE = "simple-tree-node";
  const TREE_NODE = "tree-node";

  const SOFAROUTER_TEXT_CLASS = "sofarouter-text-class";
  const SOFAROUTER_RECT_CLASS = "sofarouter-rect-class";

  const CANVAS_WIDTH = 1000;
  const CANVAS_HEIGHT = 600;

  const LIMIT_OVERFLOW_WIDTH = CANVAS_WIDTH - 100;
  const LIMIT_OVERFLOW_HEIGHT = CANVAS_HEIGHT - 100;

  const TIP_HEIGHT = 28;
  const getNodeConfig = node => {
    if (node.nodeError) {
      return {
        basicColor: ERROR_COLOR,
        fontColor: "#FFF",
        borderColor: ERROR_COLOR,
        bgColor: "#E66A6C"
      };
    }
    let config = {
      basicColor: "#722ED1",
      fontColor: "#722ED1",
      borderColor: "#722ED1",
      bgColor: "#F6EDFC"
    };
    switch (node.type) {
      case "root": {
        config = {
          basicColor: "#E3E6E8",
          fontColor: "rgba(0,0,0,0.85)",
          borderColor: "#E3E6E8",
          bgColor: "#F7F9FA"
        };
        break;
      }
      case "httpclient":
      case "rest":
      case "mvc":
      case "rpc":
      case "rpc2jvm":
        config = {
          basicColor: "#2F54EB",
          fontColor: "#2F54EB",
          borderColor: "#2F54EB",
          bgColor: "#F3F6FD"
        };
        break;
      case "db":
        config = {
          basicColor: "#52C41A",
          fontColor: "#52C41A",
          borderColor: "#52C41A",
          bgColor: "#F4FCEB"
        };
        break;
      case "msgPub":
      case "msgSub":
      case "zqmsgSend":
      case "zqmsgRecv":
      case "antqPub":
      case "antqSub":
        config = {
          basicColor: "#FA8C16",
          fontColor: "#FA8C16",
          borderColor: "#FA8C16",
          bgColor: "#FCF4E3"
        };
        break;
      case "zdalTair":
      case "zdalOcs":
      case "zdalOss":
      default:
        break;
    }
    return config;
  };

  const COLLAPSE_ICON = function COLLAPSE_ICON(x, y, r) {
    return [
      ["M", x - r, y],
      ["a", r, r, 0, 1, 0, r * 2, 0],
      ["a", r, r, 0, 1, 0, -r * 2, 0],
      ["M", x - r + 4, y],
      ["L", x - r + 2 * r - 4, y]
    ];
  };
  const EXPAND_ICON = function EXPAND_ICON(x, y, r) {
    return [
      ["M", x - r, y],
      ["a", r, r, 0, 1, 0, r * 2, 0],
      ["a", r, r, 0, 1, 0, -r * 2, 0],
      ["M", x - r + 4, y],
      ["L", x - r + 2 * r - 4, y],
      ["M", x - r + r, y - r + 4],
      ["L", x, y + r - 4]
    ];
  };
  /* 精简节点和复杂节点共用的一些方法 */
  const nodeBasicMethod = {
    createNodeBox: (group, config, width, height, isRoot) => {
      /* 最外面的大矩形 */
      const container = group.addShape("rect", {
        attrs: {
          x: 0,
          y: 0,
          width,
          height
          // fill: '#FFF',
          // stroke: '#000',
        }
      });
      if (!isRoot) {
        /* 左边的小圆点 */
        group.addShape("circle", {
          attrs: {
            x: 3,
            y: height / 2,
            r: 6,
            fill: config.basicColor
          }
        });
      }
      /* 矩形 */
      group.addShape("rect", {
        attrs: {
          x: 3,
          y: 0,
          width: width - 19,
          height,
          fill: config.bgColor,
          stroke: config.borderColor,
          radius: 2,
          cursor: "pointer"
        }
      });

      /* 左边的粗线 */
      group.addShape("rect", {
        attrs: {
          x: 3,
          y: 0,
          width: 3,
          height,
          fill: config.basicColor,
          radius: 1.5
        }
      });
      return container;
    },
    /* 生成树上的 marker */
    createNodeMarker: (group, collapsed, x, y) => {
      group.addShape("circle", {
        attrs: {
          x,
          y,
          r: 13,
          fill: "rgba(47, 84, 235, 0.05)",
          opacity: 0,
          zIndex: -2
        },
        className: "collapse-icon-bg"
      });
      group.addShape("marker", {
        attrs: {
          x,
          y,
          radius: 7,
          symbol: collapsed ? EXPAND_ICON : COLLAPSE_ICON,
          stroke: "rgba(0,0,0,0.25)",
          fill: "rgba(0,0,0,0)",
          lineWidth: 1,
          cursor: "pointer"
        },
        className: "collapse-icon"
      });
    },
    afterDraw: (cfg, group) => {
      /* 操作 marker 的背景色显示隐藏 */
      const icon = group.findByClassName("collapse-icon");
      if (icon) {
        const bg = group.findByClassName("collapse-icon-bg");
        icon.on("mouseenter", () => {
          bg.attr("opacity", 1);
          graph.get("canvas").draw();
        });
        icon.on("mouseleave", () => {
          bg.attr("opacity", 0);
          graph.get("canvas").draw();
        });
      }
      /* ip 显示 */
      const ipBox = group.findByClassName("ip-box");
      if (ipBox) {
        /* ip 复制的几个元素 */
        const ipLine = group.findByClassName("ip-cp-line");
        const ipBG = group.findByClassName("ip-cp-bg");
        const ipIcon = group.findByClassName("ip-cp-icon");
        const ipCPBox = group.findByClassName("ip-cp-box");

        const onMouseEnter = () => {
          this.ipHideTimer && clearTimeout(this.ipHideTimer);
          ipLine.attr("opacity", 1);
          ipBG.attr("opacity", 1);
          ipIcon.attr("opacity", 1);
          graph.get("canvas").draw();
        };
        const onMouseLeave = () => {
          this.ipHideTimer = setTimeout(() => {
            ipLine.attr("opacity", 0);
            ipBG.attr("opacity", 0);
            ipIcon.attr("opacity", 0);
            graph.get("canvas").draw();
          }, 100);
        };
        ipBox.on("mouseenter", () => {
          onMouseEnter();
        });
        ipBox.on("mouseleave", () => {
          onMouseLeave();
        });
        ipCPBox.on("mouseenter", () => {
          onMouseEnter();
        });
        ipCPBox.on("mouseleave", () => {
          onMouseLeave();
        });
        ipCPBox.on("click", () => {});
      }
    },
    setState: (name, value, item) => {
      const hasOpacityClass = [
        "ip-cp-line",
        "ip-cp-bg",
        "ip-cp-icon",
        "ip-cp-box",
        "ip-box",
        "collapse-icon-bg"
      ];
      const group = item.getContainer();
      const childrens = group.get("children");
      graph.setAutoPaint(false);
      if (name === "emptiness") {
        if (value) {
          childrens.forEach(shape => {
            if (hasOpacityClass.indexOf(shape.get("className")) > -1) {
              return;
            }
            shape.attr("opacity", 0.4);
          });
        } else {
          childrens.forEach(shape => {
            if (hasOpacityClass.indexOf(shape.get("className")) > -1) {
              return;
            }
            shape.attr("opacity", 1);
          });
        }
      }
      graph.setAutoPaint(true);
    }
  };

  /* 精简节点 */
  G6.registerNode(
    SIMPLE_TREE_NODE,
    {
      drawShape: (cfg, group) => {
        const config = getNodeConfig(cfg);
        const isRoot = cfg.type === "root";
        const nodeError = cfg.nodeError;

        const container = nodeBasicMethod.createNodeBox(
          group,
          config,
          171,
          38,
          isRoot
        );

        /* name */
        const nameText = group.addShape("text", {
          attrs: {
            text: cfg.name,
            x: 19,
            y: 19,
            fontSize: 14,
            fontWeight: 700,
            textAlign: "left",
            textBaseline: "middle",
            fill: config.fontColor,
            cursor: "pointer"
          }
        });

        /* 修复 nameText 超长 */
        fittingString(nameText, cfg.name, 133);

        if (nodeError) {
          group.addShape("image", {
            attrs: {
              x: 119,
              y: 5,
              height: 35,
              width: 35,
              img: "/static/images/warning-circle.svg"
            }
          });
        }

        const hasChildren = cfg.children && cfg.children.length > 0;
        if (hasChildren) {
          nodeBasicMethod.createNodeMarker(group, cfg.collapsed, 164, 19);
        }
        return container;
      },
      afterDraw: nodeBasicMethod.afterDraw,
      setState: nodeBasicMethod.setState
    },
    "single-shape"
  );

  /* 复杂节点 */
  G6.registerNode(
    TREE_NODE,
    {
      drawShape: (cfg, group) => {
        const config = getNodeConfig(cfg);
        const isRoot = cfg.type === "root";
        const nodeError = cfg.nodeError;
        /* 最外面的大矩形 */
        const container = nodeBasicMethod.createNodeBox(
          group,
          config,
          243,
          64,
          isRoot
        );

        if (cfg.type !== "root") {
          /* 上边的 type */
          group.addShape("text", {
            attrs: {
              text: cfg.type,
              x: 3,
              y: -10,
              fontSize: 12,
              textAlign: "left",
              textBaseline: "middle",
              fill: "rgba(0,0,0,0.65)"
            }
          });
        }

        let ipWidth = 0;
        if (cfg.ip) {
          /* ip start */
          /* ipBox */
          const ipRect = group.addShape("rect", {
            attrs: {
              fill: nodeError ? null : "#FFF",
              stroke: nodeError ? "rgba(255,255,255,0.65)" : null,
              radius: 2,
              cursor: "pointer"
            }
          });

          /* ip */
          const ipText = group.addShape("text", {
            attrs: {
              text: cfg.ip,
              x: 0,
              y: 19,
              fontSize: 12,
              textAlign: "left",
              textBaseline: "middle",
              fill: nodeError ? "rgba(255,255,255,0.85)" : "rgba(0,0,0,0.65)",
              cursor: "pointer"
            }
          });

          const ipBBox = ipText.getBBox();
          /* ip 的文字总是距离右边 12px */
          ipText.attr({
            x: 224 - 12 - ipBBox.width
          });
          /* ipBox */
          ipRect.attr({
            x: 224 - 12 - ipBBox.width - 4,
            y: ipBBox.minY - 5,
            width: ipBBox.width + 8,
            height: ipBBox.height + 10
          });

          /* 在 IP 元素上面覆盖一层透明层，方便监听 hover 事件 */
          group.addShape("rect", {
            attrs: {
              stroke: "",
              cursor: "pointer",
              x: 224 - 12 - ipBBox.width - 4,
              y: ipBBox.minY - 5,
              width: ipBBox.width + 8,
              height: ipBBox.height + 10,
              fill: "#fff",
              opacity: 0
            },
            className: "ip-box"
          });

          /* copyIpLine */
          group.addShape("rect", {
            attrs: {
              x: 194,
              y: 7,
              width: 1,
              height: 24,
              fill: "#E3E6E8",
              opacity: 0
            },
            className: "ip-cp-line"
          });
          /* copyIpBG */
          group.addShape("rect", {
            attrs: {
              x: 195,
              y: 8,
              width: 22,
              height: 22,
              fill: "#FFF",
              cursor: "pointer",
              opacity: 0
            },
            className: "ip-cp-bg"
          });
          /* copyIpIcon */
          group.addShape("image", {
            attrs: {
              x: 200,
              y: 13,
              height: 12,
              width: 10,
              img: "https://os.alipayobjects.com/rmsportal/DFhnQEhHyPjSGYW.png",
              cursor: "pointer",
              opacity: 0
            },
            className: "ip-cp-icon"
          });
          /* 放一个透明的矩形在 icon 区域上，方便监听点击 */
          group.addShape("rect", {
            attrs: {
              x: 195,
              y: 8,
              width: 22,
              height: 22,
              fill: "#FFF",
              cursor: "pointer",
              opacity: 0
            },
            className: "ip-cp-box",
            tooltip: "复制IP"
          });

          const ipRectBBox = ipRect.getBBox();
          ipWidth = ipRectBBox.width;
          /* ip end */
        }

        /* name */
        const nameText = group.addShape("text", {
          attrs: {
            text: cfg.name,
            x: 19,
            y: 19,
            fontSize: 14,
            fontWeight: 700,
            textAlign: "left",
            textBaseline: "middle",
            fill: config.fontColor,
            cursor: "pointer"
          }
          // tooltip: cfg.name,
        });

        /* 根据 IP 的长度计算出 剩下的 留给 name 的长度！ */
        /* 修复 nameText 超长 */
        fittingString(nameText, cfg.name, 224 - ipWidth - 20);

        /* 下面的文字 */
        const remarkText = group.addShape("text", {
          attrs: {
            text: cfg.keyInfo,
            x: 19,
            y: 45,
            fontSize: 14,
            textAlign: "left",
            textBaseline: "middle",
            fill: config.fontColor,
            cursor: "pointer"
          }
          // className: 'keyInfo',
          // tooltip: cfg.keyInfo,
        });
        fittingString(remarkText, cfg.keyInfo, 204);

        if (nodeError) {
          group.addShape("image", {
            attrs: {
              x: 191,
              y: 32,
              height: 35,
              width: 35,
              img: "/static/images/warning-circle.svg"
            }
          });
        }

        const hasChildren = cfg.children && cfg.children.length > 0;
        if (hasChildren) {
          nodeBasicMethod.createNodeMarker(group, cfg.collapsed, 236, 32);
        }
        return container;
      },
      afterDraw: nodeBasicMethod.afterDraw,
      setState: nodeBasicMethod.setState
    },
    "single-shape"
  );
  /* 是否显示 sofarouter，通过透明度来控制 */
  G6.registerEdge(
    "tree-edge",
    {
      draw(cfg, group) {
        const targetNode = cfg.targetNode.getModel();
        const edgeError = !!targetNode.edgeError;

        const startPoint = cfg.startPoint;
        const endPoint = cfg.endPoint;
        const controlPoints = this.getControlPoints(cfg);
        let points = [startPoint]; // 添加起始点
        // 添加控制点
        if (controlPoints) {
          points = points.concat(controlPoints);
        }
        // 添加结束点
        points.push(endPoint);
        const path = this.getPath(points);

        group.addShape("path", {
          attrs: {
            path,
            lineWidth: 12,
            stroke: edgeError
              ? "rgba(245,34,45,0.05)"
              : "rgba(47,84,235,0.05)",
            opacity: 0,
            zIndex: 0
          },
          className: "line-bg"
        });
        const keyShape = group.addShape("path", {
          attrs: {
            path,
            lineWidth: 1,
            stroke: edgeError ? "#FF7875" : "rgba(0,0,0,0.25)",
            zIndex: 1,
            lineAppendWidth: 12
          },
          edgeError: !!edgeError
        });

        /* 连接线的中间点 */
        const centerPoint = {
          x: startPoint.x + (endPoint.x - startPoint.x) / 2,
          y: startPoint.y + (endPoint.y - startPoint.y) / 2
        };
        const textRect = group.addShape("rect", {
          attrs: {
            fill: "#FFF1F0",
            radius: 2,
            cursor: "pointer",
            opacity: 1
          },
          /* sofarouter 需要 class，以便控制 显示隐藏*/
          className: SOFAROUTER_RECT_CLASS
        });
        const text = group.addShape("text", {
          attrs: {
            text: "edge-label",
            x: 0,
            y: 0,
            fontSize: 12,
            textAlign: "left",
            textBaseline: "middle",
            fill: "#F5222D",
            opacity: 1
          },
          /* sofarouter 需要 class，以便控制 显示隐藏*/
          className: SOFAROUTER_TEXT_CLASS
        });
        const textBBox = text.getBBox();
        /* text 的位置 */
        text.attr({
          x: centerPoint.x - textBBox.width / 2,
          y: centerPoint.y
        });
        /* text 的框框 */
        textRect.attr({
          x: centerPoint.x - textBBox.width / 2 - 4,
          y: centerPoint.y - textBBox.height / 2 - 5,
          width: textBBox.width + 8,
          height: textBBox.height + 10
        });

        return keyShape;
      },
      /* 操作 线 的背景色显示隐藏 */
      afterDraw: (cfg, group) => {
        /* 背景色 */
        const lineBG = group.get("children")[0]; // 顺序根据 draw 时确定
        /* 线条 */
        const line = group.get("children")[1];
        line.on("mouseenter", () => {
          lineBG.attr("opacity", "1");
          /* 线条如果在没有错误的情况下，在 hover 时候，是需要变成蓝色的 */
          if (!line.get("edgeError")) {
            line.attr("stroke", "#2F54EB");
          }
          graph.get("canvas").draw();
        });
        line.on("mouseleave", () => {
          lineBG.attr("opacity", "0");
          if (!line.get("edgeError")) {
            line.attr("stroke", "rgba(0,0,0,0.25)");
          }
          graph.get("canvas").draw();
        });
      },
      setState: (name, value, item) => {
        const group = item.getContainer();
        const childrens = group.get("children");
        graph.setAutoPaint(true);
        if (name === "emptiness") {
          if (value) {
            childrens.forEach(shape => {
              if (shape.get("className") === "line-bg") {
                return;
              }
              shape.attr("opacity", 0.4);
            });
          } else {
            childrens.forEach(shape => {
              if (shape.get("className") === "line-bg") {
                return;
              }
              shape.attr("opacity", 1);
            });
          }
        }
        graph.setAutoPaint(true);
      },
      update: null
    },
    "cubic-horizontal"
  );
  G6.registerBehavior("three-finger-drag-canvas", {
    getEvents() {
      return {
        "canvas:dragstart": "onDragStart",
        "canvas:drag": "onDrag",
        "canvas:dragend": "onDragEnd"
      };
    },
    onDragStart: ev => {
      ev.preventDefault();
      this.dragDx = ev.x;
      this.dragDy = ev.y;
    },
    onDrag: ev => {
      ev.preventDefault();
      translate(this.dragDx - ev.x, this.dragDy - ev.y);
    },
    onDragEnd: ev => {
      ev.preventDefault();
    }
  });
  G6.registerBehavior("double-finger-drag-canvas", {
    getEvents() {
      return {
        wheel: "onWheel"
      };
    },
    onWheel: ev => {
      if (ev.ctrlKey) {
        const canvas = graph.get("canvas");
        const pixelRatio = canvas.get("pixelRatio");
        const point = canvas.getPointByClient(ev.clientX, ev.clientY);
        let ratio = graph.getZoom();
        if (ev.wheelDelta > 0) {
          ratio = ratio + ratio * 0.05;
        } else {
          ratio = ratio - ratio * 0.05;
        }
        graph.zoomTo(ratio, {
          x: point.x / pixelRatio,
          y: point.y / pixelRatio
        });
      } else {
        const x = ev.deltaX || ev.movementX;
        const y = ev.deltaY || ev.movementY;
        translate(x, y);
      }
      ev.preventDefault();
    }
  });
   /*G6.registerBehavior("tooltip", {
    getEvents() {
      return {
        "node:mousemove": "onMouseMove",
        "node:mouseeleave": "onMouseLeave"
      };
    },
    onMouseMove: ev => {
      const {
        tooltip: { visible }
      } = this.state;
      const tooltip = ev.target.get("tooltip");
      const group = ev.item.get("group");
      if (tooltip && !visible) {
        const bbox = ev.target.getBBox();
        const matrix = group.getMatrix();
        const zoom = graph.getZoom();
        this.showTooltip(
          tooltip,
          matrix[6] + bbox.x + bbox.width / 2,
          matrix[7] + bbox.y - TIP_HEIGHT / zoom
        );
      }
      if (!tooltip && visible) {
        this.closeTooptip();
      }
    },
    onMouseLeave: () => {
      const {
        tooltip: { visible }
      } = this.state;
      if (visible) {
        this.closeTooptip();
      }
    }
  });*/
  const minimap = new Minimap({
    size: [184, 124],
    className: "minimap",
    type: 'delegate'
  });
  let selectedItem;
  graph = new G6.TreeGraph({
    container: "mountNode",
    width: CANVAS_WIDTH,
    height: CANVAS_HEIGHT,
    plugins: [minimap],
    modes: {
      default: [
        {
          type: "collapse-expand",
          shouldUpdate: function shouldUpdate(e) {
            /* 点击 node 禁止展开收缩 */
            if (e.target.get("className") !== "collapse-icon") {
              return false;
            }
            return true;
          },
          onChange: function onChange(item, collapsed) {
            selectedItem = item;
            const icon = item.get("group").findByClassName("collapse-icon");
            if (collapsed) {
              icon.attr("symbol", EXPAND_ICON);
            } else {
              icon.attr("symbol", COLLAPSE_ICON);
            }
          },
          animate: {
            callback: () => {
              debugger;
              graph.focusItem(selectedItem);
            }
          }
        },
        "double-finger-drag-canvas",
        "three-finger-drag-canvas",
        { type: "tooltip",
          formatText: data => {
            return `<div>${data.name}</div>`
          }
        },
        {
          type: "drag-canvas",
          shouldUpdate: function shouldUpdate() {
            return false;
          },
          shouldEnd: function shouldUpdate() {
            return false;
          }
        }
      ]
    },
    defaultNode: {
      shape: TREE_NODE,
      anchorPoints: [[0, 0.5], [1, 0.5]]
    },
    defaultEdge: {
      shape: "tree-edge"
    },
    edgeStyle: {
      default: {
        stroke: "#A3B1BF"
      }
    },
    layout: {
      type: 'compactBox',
      direction: "LR",
        getId: function getId(d) {
          return d.id;
        },
        getWidth: () => {
          return 243;
        },
        getVGap: function getVGap() {
          return 24;
        },
        getHGap: function getHGap() {
          return 50;
        }
    }
  });
  function strLen(str) {
    var len = 0;
    for (var i = 0; i < str.length; i ++) {
      if(str.charCodeAt(i) > 0 && str.charCodeAt(i) < 128) {
        len ++;
      } else {
        len += 2;
      }
    }
    return len;
  }
  function fittingString (c, str, maxWidth) {
    const width = strLen(str) * 8;
    const ellipsis = "…";
    if (width > maxWidth) {
      const actualLen = Math.floor((maxWidth - 10) / 8);
      c.attr('text', str.substring(0, actualLen) + ellipsis);
      c._cfg.tooltip = str;
    }
  };
  function translate(x, y) {
    // graph.translate(-x, -y);
    let moveX = x;
    let moveY = y;

    const containerWidth = graph.get("width");
    const containerHeight = graph.get("height");

    /* 获得当前偏移量*/
    const group = graph.get("group");
    const bbox = group.getBBox();
    const leftTopPoint = graph.getCanvasByPoint(bbox.minX, bbox.minY);
    const rightBottomPoint = graph.getCanvasByPoint(bbox.maxX, bbox.maxY);
    /* 如果 x 轴在区域内，不允许左右超过100 */
    if (x < 0 && leftTopPoint.x - x > LIMIT_OVERFLOW_WIDTH) {
      moveX = 0;
    }
    if (
      x > 0 &&
      rightBottomPoint.x - x < containerWidth - LIMIT_OVERFLOW_WIDTH
    ) {
      moveX = 0;
    }

    if (y < 0 && leftTopPoint.y - y > LIMIT_OVERFLOW_HEIGHT) {
      moveY = 0;
    }
    if (
      y > 0 &&
      rightBottomPoint.y - y < containerHeight - LIMIT_OVERFLOW_HEIGHT
    ) {
      moveY = 0;
    }
    graph.translate(-moveX, -moveY);
  };
  function fitView() {
    const group = graph.get("group");
    const width = graph.get("width"); // 视窗的宽度
    const height = graph.get("height"); // 视窗的高度
    group.resetMatrix();
    // 内容
    const bbox = group.getBBox();
    // 视窗中间
    const viewCenter = {
      x: width / 2,
      y: height / 2
    };
    /* 内容的中点 */
    const groupCenter = {
      x: bbox.x + bbox.width / 2,
      y: bbox.y + bbox.height / 2
    };
    graph.translate(-bbox.x + 16, viewCenter.y - groupCenter.y);
  };
  function zoomTo(ratio, center) {
    const width = graph.get("width"); // 视窗的宽度
    const height = graph.get("height"); // 视窗的高度
    const viewCenter = {
      x: width / 2,
      y: height / 2
    };
    graph.zoomTo(ratio, center || viewCenter);
  };
  function formatData(data) {
      const recursiveTraverse = (node, level = 0) => {
        const appName = "testappName";
        const keyInfo = "testkeyinfo";
        const ip = "111.22.33.44";

        const targetNode = {
          id: node.key + "",
          rpcId: node.rpcId,
          level,
          type: node.appName === "USER" ? "root" : node.type,
          name: appName,
          keyInfo: keyInfo || "-",
          ip,
          nodeError: false,
          edgeError: false,
          sofarouter: true,
          sofarouterError: true,
          asyn: false,
          origin: node
        };
        if (node.children) {
          targetNode.children = [];
          node.children.forEach(item => {
            targetNode.children.push(recursiveTraverse(item, level + 1));
          });
        }
        return targetNode;
      };
      const result = recursiveTraverse(data);
      return result;
  }
  graph.on('beforepaint', () => {
    const topLeft = graph.getPointByCanvas(0, 0);
    const bottomRight = graph.getPointByCanvas(1000, 600);
    graph.getNodes().forEach(node => {
      const model = node.getModel();
      if (model.x  < topLeft.x - 200 || model.x > bottomRight.x || model.y < topLeft.y || model.y > bottomRight.y) {
        node.getContainer().hide();
      } else {
        node.getContainer().show();
      }
    });
    const edges = graph.getEdges();
    edges.forEach(edge => {
      const sourceNode = edge.get('sourceNode');
      const targetNode = edge.get('targetNode');
      if (!sourceNode.get('visible') && !targetNode.get('visible')) {
        edge.hide();
      } else {
        edge.show();
      }
    })
  });
  $.getJSON('./assets/sourceData.json', data => {
    data = formatData(data);
    graph.data(data);
    graph.render();
    graph.fitView();
  });
</script>
</body>
</html>
